package ai.neuralnet;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public abstract class NNTrainer<NN extends NeuralNetwork<?>>
{

    protected int maxIterations = 2000;
    protected int trainingCount = 10;
    protected double targetAccuracy = 0.8;
    protected double minRandomWeight = -1;
    protected double maxRandomWeight = 1;
    protected ArrayList<NN> networks;
    protected NNBuilder<?, NN> networkBuilder;
    protected ClassificationDataSet validationSet;
    protected ClassificationDataSet trainingSet;
    protected boolean printTrainingProgress = false;
    protected int printFrequency = 10;

    public NNTrainer(NNBuilder<?, NN> networkBuilder)
    {
        this.networkBuilder = networkBuilder;
        networks = new ArrayList<NN>();
    }

    public abstract void trainNetworks();

    protected String statsString(double[] accuracy)
    {
        DecimalFormat format = new DecimalFormat("#.##%");
        return "Stats: [Accuracy: " + format.format(accuracy[0]) + " Confidence: " + format.format(accuracy[1]) + " ]";
    }

    protected ArrayList<NN> createRandomNetworks(int quantity)
    {
        ArrayList<NN> randNets = new ArrayList<NN>();
        for (int i = 0; i < quantity; i++)
        {
            NN network = networkBuilder.build();
            network.randomizeWeights(minRandomWeight, maxRandomWeight);
            randNets.add(network);

        }

        return randNets;
    }


    public double[] validationAccuracies()
    {
        double[] accs = new double[networks.size()];
        int i = 0;
        for (NN network : networks)
        {
            accs[i++] = validationSet.getAccuracy(network)[0];
        }

        return accs;
    }

    /**
     * validates the trained networks against the validation data set
     *
     * @return the network that scores highest on validation for best guess accuracy
     */
    public NN validate()
    {


        NN best = null;
        double max = 0;

        int i = 1, b = -1;
        for (NN network : networks)
        {
            double[] accuracy = validationSet.getAccuracy(network);
            System.out.println("Network " + (i++) + " Validation " + statsString(accuracy));

            if (accuracy[0] > max)
            {
                best = network;
                max = accuracy[0];
                b = i - 1;
            }
        }

        System.out.println("Best Network: " + b);

        return best;
    }

    /**
     * Sets the number of networks to train before the validation phase
     *
     * @param trainingCount
     */
    public void setTrainingCount(int trainingCount)
    {
        this.trainingCount = trainingCount;
    }

    public void setValidationSet(ClassificationDataSet validationSet)
    {
        this.validationSet = validationSet;
    }

    public void setTrainingSet(ClassificationDataSet trainingSet)
    {
        this.trainingSet = trainingSet;
    }

    public void setTargetAccuracy(double targetAccuracy)
    {
        this.targetAccuracy = targetAccuracy;
    }

    public void setPrintTrainingProgress(boolean printTrainingProgress)
    {
        this.printTrainingProgress = printTrainingProgress;
    }

    public void setMinRandomWeight(double minRandomWeight)
    {
        this.minRandomWeight = minRandomWeight;
    }

    public void setMaxRandomWeight(double maxRandomWeight)
    {
        this.maxRandomWeight = maxRandomWeight;
    }

    public void setPrintPeriod(int printFrequency)
    {
        this.printFrequency = printFrequency;
    }

    public void setMaxIterations(int maxIterations)
    {
        this.maxIterations = maxIterations;
    }

    public void setNetworks(Collection<? extends NN> votersList)
    {
        networks.clear();
        networks.addAll(votersList);
    }

    public ClassificationDataSet getTrainingSet()
    {
        return trainingSet;
    }

    public ClassificationDataSet getValidationSet()
    {
        return validationSet;
    }

    public List<NN> getNetworks()
    {
        return networks;
    }
}
